﻿using System;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using TestByWire.Factories;
using TestByWire.Test.Fakes;

namespace TestByWire.Test.Factories
{
	[TestClass]
	public class StubInstanceFactoryTest
	{
		private IUnityContainer _container;
		private StubInstanceFactory _stubInstanceFactory;
		private IStubCreationStrategyFactory _stubCreationStrategyFactory;

		[TestInitialize]
		public void PerTestSetup()
		{
			_container = new UnityContainer();
			_stubCreationStrategyFactory = MockRepository.GenerateStub<IStubCreationStrategyFactory>();
			_stubInstanceFactory = new StubInstanceFactory(_stubCreationStrategyFactory, _container);
		}

		[TestMethod]
		public void ConstructorMustNotAcceptNullAsStubCreationStrategyFactory()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => new StubInstanceFactory(null, _container));

			Assert.AreEqual("stubCreationStrategyFactory", exception.ParamName);
		}

		[TestMethod]
		public void ConstructorMustNotAcceptNullAsContainer()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => new StubInstanceFactory(_stubCreationStrategyFactory, null));

			Assert.AreEqual("container", exception.ParamName);
		}

		[TestMethod]
		public void CreateStubInstanceOfMustNotAcceptNullAsTypeToCreate()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => _stubInstanceFactory.CreateStubInstanceOf(null, null));

			Assert.AreEqual("typeToCreate", exception.ParamName);
		}

		[TestMethod]
		public void CreateStubInstanceMustReturnCreatedStub()
		{
			var strategy = MockRepository.GenerateStub<IStubCreationStrategy>();
			var theInstance = MockRepository.GenerateStub<Calculator>();
			strategy.Stub(s => s.CreateInstance(Arg<Type>.Is.Equal(typeof(Calculator)), Arg<string>.Is.Anything)).Return(theInstance);
			_stubCreationStrategyFactory.Stub(s => s.DetermineStrategy(Arg<Type>.Is.Anything, Arg<string>.Is.Anything)).Return(strategy);

			var instance = _stubInstanceFactory.CreateStubInstanceOf(typeof(Calculator), null);

			Assert.IsNotNull(instance);
		}

		[TestMethod]
		public void CreateStubInstanceMustRegisterNewStubOnContainer()
		{
			var strategy = MockRepository.GenerateStub<IStubCreationStrategy>();
			strategy.Stub(s => s.RegisterOnContainer).Return(true);
			var theInstance = MockRepository.GenerateStub<Calculator>();
			strategy.Stub(s => s.CreateInstance(Arg<Type>.Is.Equal(typeof(Calculator)), Arg<string>.Is.Anything)).Return(theInstance);
			_stubCreationStrategyFactory.Stub(s => s.DetermineStrategy(Arg<Type>.Is.Anything, Arg<string>.Is.Anything)).Return(strategy);

			_stubInstanceFactory.CreateStubInstanceOf(typeof(Calculator), null);

			Assert.IsTrue(_container.IsRegistered<Calculator>());
		}

		[TestMethod]
		public void CreateStubInstanceOfMustNotRegisterStubOnContainerIfStrategyDoesNotAllowForIt()
		{
			var strategy = MockRepository.GenerateStub<IStubCreationStrategy>();
			strategy.Stub(s => s.RegisterOnContainer).Return(false);
			var theInstance = MockRepository.GenerateStub<Calculator>();
			strategy.Stub(s => s.CreateInstance(Arg<Type>.Is.Equal(typeof(Calculator)), Arg<string>.Is.Anything)).Return(theInstance);
			_stubCreationStrategyFactory.Stub(s => s.DetermineStrategy(Arg<Type>.Is.Anything, Arg<string>.Is.Anything)).Return(strategy);

			_stubInstanceFactory.CreateStubInstanceOf(typeof(Calculator), null);

			Assert.IsFalse(_container.IsRegistered<Calculator>());
		}

		[TestMethod]
		public void CreateStubInstanceOfMustThrowExceptionIfStubCannotBeCreated()
		{
			var strategy = MockRepository.GenerateStub<IStubCreationStrategy>();
			_stubCreationStrategyFactory.Stub(s => s.DetermineStrategy(Arg<Type>.Is.Anything, Arg<string>.Is.Anything)).Return(strategy);

			var exception = AssertEx.Throws<InvalidOperationException>(() => _stubInstanceFactory.CreateStubInstanceOf(typeof(Calculator), null));

			Assert.IsTrue(exception.Message.StartsWith(@"Unable to create stub instance of type ", StringComparison.OrdinalIgnoreCase));
		}

		[TestMethod]
		public void CreateStubActionMustNotAcceptNullAsInstanceBeingStubbed()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => _stubInstanceFactory.CreateStubAction(null, this.GetType().GetMethod("PerTestSetup")));

			Assert.AreEqual("instanceBeingStubbed", exception.ParamName);
		}

		[TestMethod]
		public void CreateStubActionMustNotAcceptNullAsMethodInfo()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => _stubInstanceFactory.CreateStubAction(this, null));

			Assert.AreEqual("methodInfo", exception.ParamName);
		}

		[TestMethod]
		public void CreatePropertySetAction_MustThrow_WhenInstanceBeingStubbedIsNull()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => _stubInstanceFactory.CreatePropertySetAction(null, GetType().GetMethod("PerTestSetup"), this));

			Assert.AreEqual("instanceBeingStubbed", exception.ParamName);
		}


		[TestMethod]
		public void CreatePropertySetAction_MustThrow_WhenSetterMethodIsNull()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => _stubInstanceFactory.CreatePropertySetAction(this, null, this));

			Assert.AreEqual("setterMethod", exception.ParamName);
		}

		[TestMethod]
		public void CreatePropertySetAction_MustThrow_WhenPropertyValueIsNull()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => _stubInstanceFactory.CreatePropertySetAction(this, GetType().GetMethod("PerTestSetup"), null));

			Assert.AreEqual("propertyValue", exception.ParamName);
		}
		
		[TestMethod]
		public void CreatePropertySetAction_MustReturnAction_WhenCalledWithValidParameters()
		{
			var instance = MockRepository.GenerateStub<IMemory>();
			var theValue = MockRepository.GenerateStub<IResult>();

			var action = _stubInstanceFactory.CreatePropertySetAction(instance, instance.GetType().GetMethod("set_StoredValue"), theValue);

			Assert.IsNotNull(action);
		}
		
		[TestMethod]
		public void CreateStubAction()
		{
			var instanceBeingStubbed = MockRepository.GenerateStub<CalculatorResult>(2);

			var action = _stubInstanceFactory.CreateStubAction(instanceBeingStubbed, this.GetType().GetMethod("PerTestSetup"));

			Assert.IsNotNull(action);
		}
	}
}
